package com.wkk.utils;

import org.springframework.beans.factory.annotation.*;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.*;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@SuppressWarnings("all")
public class RedisUtil<T> {

    @Autowired
    @Qualifier("myRedisTemplate")
    private RedisTemplate<String,Object> redisTemplate;


    //==========================common==================


    /**
     * 删除key中带有该条件的缓存
     * @param condition
     */
    public void delKeyByCondition(String condition){
        String include  = "*"+condition +"*";
        Set<String> keys = redisTemplate.keys(include);
        for(String key :keys){
            del(key);
        }
    }


    /**
     * 加锁(分布式锁)
     * @param lockKey 加锁的Key
     * @param timeStamp 时间戳：当前时间+超时时间
     * @return
     */
    public boolean lock(String lockKey,String timeStamp){
        if(redisTemplate.opsForValue().setIfAbsent(lockKey,timeStamp)){
            // 对应setnx命令，可以成功设置,也就是key不存在，获得锁成功
            // 同时这是整个锁的有效时间，以防出现访问时的宕机
            redisTemplate.expire(lockKey,200, TimeUnit.MILLISECONDS);
            return true;
        }
        // 设置失败，获得锁失败
        // 判断锁超时 - 防止原来的操作异常，没有运行解锁操作 ，防止死锁
        String currentLock = (String) redisTemplate.opsForValue().get(lockKey);
        // 如果锁过期 currentLock不为空且小于当前时间
        if(!StringUtils.isEmpty(currentLock) && Long.parseLong(currentLock) < System.currentTimeMillis()){
            // 如果lockKey对应的锁已经存在，获取上一次设置的时间戳之后并重置lockKey对应的锁的时间戳
            String preLock = (String)redisTemplate.opsForValue().getAndSet(lockKey, timeStamp);

            // 假设两个线程同时进来这里，因为key被占用了，而且锁过期了。
            // 获取的值currentLock=A(get取的旧的值肯定是一样的),两个线程的timeStamp都是B,key都是K.锁时间已经过期了。
            // 而这里面的getAndSet一次只会一个执行，也就是一个执行之后，上一个的timeStamp已经变成了B。
            // 只有一个线程获取的上一个值会是A，另一个线程拿到的值是B。
            if(!StringUtils.isEmpty(preLock) && preLock.equals(currentLock)){
                return true;
            }
        }
        return false;
    }

    /**
     * 释放锁
     * @param lockKey
     * @param timeStamp
     */
    public void release(String lockKey,String timeStamp){
        try {
            String currentValue = redisTemplate.opsForValue().get(lockKey)+"";
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(timeStamp) ){
                // 删除锁状态
                redisTemplate.opsForValue().getOperations().delete(lockKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("警报！警报！警报！解锁异常");
        }
    }


    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间
     * @return
     */
    public boolean expire(String key,long time){
        try{
            if(time>0){
                redisTemplate.expire(key,time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间（秒） 返回0代表永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false 不存在
     */
    public boolean hasKey(String key){
        try{
            return redisTemplate.hasKey(key);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    public void del(String... key){
        if(key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //========================= String =======================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public T get(String key){
        return key == null ? null :(T)redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true 成功 false 失败
     */
    public boolean set(String key,Object value){
        try{
            redisTemplate.opsForValue().set(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间（秒） time要大于0 如果小于等于0 将设置无限期
     * @return
     */
    public boolean set(String key,Object value,long time){
        try{
            if(time >0){
                redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几（大于0）
     * @return
     */
    public long incr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几（大于0）
     * @return
     */
    public long decr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //==========================  HASH  =========================

    /**
     * HashGet
     * @param key 键
     * @param item 项
     * @return
     */
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key,item);
    }

    /**
     * 获取HashKey对应的值
     * @param key 键
     * @return
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return
     */
    public boolean hmset(String key ,Map<String,Object> map){
        try{
            redisTemplate.opsForHash().putAll(key,map);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间（秒）
     * @return
     */
    public boolean hmset(String key ,Map<String,Object> map,long time){
        try{
            redisTemplate.opsForHash().putAll(key,map);
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 想一直张Hash表中放入数据，如果不存在则创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return 成功true 失败false
     */
    public boolean hset(String key,String item,Object value){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据，如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间（秒） 注意：如果已存在的hash表有时间，这里将会替换原有时间
     * @return
     */
    public boolean hset(String key,String item,Object value,long time){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使用多个，不能为null
     */
    public void hdel(String key,Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key
     * @param item
     * @return
     */
    public boolean hHasKey(String key,String item){
        return redisTemplate.opsForHash().hasKey(key,item);
    }

    /**
     * hash 递增 如果不存在，就会创建一个 并把新增后的值返回
     * @param key
     * @param item
     * @param by
     * @return
     */
    public double hincr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,by);
    }

    /**
     * hash 递减
     * @param key
     * @param item
     * @param by
     * @return
     */
    public double hdecr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,-by);
    }

    //============================set==================================

    /**
     * 根据key获取set中的所有值
     * @param key
     * @return
     */
    public Set<T> sGet(String key){
        try{
            return (Set<T>)redisTemplate.opsForSet().members(key);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询，是否存在
     * @param key
     * @param value
     * @return
     */
    public boolean sHasKey(String key,Object value){
        try{
            return redisTemplate.opsForSet().isMember(key,value);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key,Object... values){
        try{
            return redisTemplate.opsForSet().add(key,values);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间
     * @param values 值 可以是多个
     * @return 返回成功个数
     */
    public long sSetAndTime(String key,long time,Object... values){
        try{
            Long count = redisTemplate.opsForSet().add(key,values);
            if(time > 0){
                expire(key, time);
            }
            return count;
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取指定set的长度
     * @param key
     * @return
     */
    public long sGetSetSize(String key){
        try{
            return redisTemplate.opsForSet().size(key);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key
     * @param values
     * @return
     */
    public long setRemove(String key,Object... values){
        try{
            return redisTemplate.opsForSet().remove(key, values);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }
    //======================== sorted_set ======================
    public boolean zSet(String key,Object object,Double score){
        try {
            return redisTemplate.opsForZSet().add(key,object,score);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    public long zSet(String key, Set<ZSetOperations.TypedTuple<Object>> set){
        try{
            return redisTemplate.opsForZSet().add(key,set);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }

    public Set<T> zGetAsc(String key,Integer start ,Integer stop){
        return (Set<T>)redisTemplate.opsForZSet().range(key,start,stop);
    }

    public Set<T> zGetDesc(String key,Integer start ,Integer stop){
        return (Set<T>)redisTemplate.opsForZSet().reverseRange(key,start,stop);
    }

    public Set<ZSetOperations.TypedTuple<Object>> zGetAscWithScore(String key,Integer start ,Integer stop){
        return redisTemplate.opsForZSet().rangeWithScores(key,start,stop);
    }

    public Set<ZSetOperations.TypedTuple<Object>> zGetDescWithScore(String key,Integer start ,Integer stop){
        return redisTemplate.opsForZSet().reverseRangeWithScores(key,start,stop);
    }



    //========================= list ==============================

    /**
     * 获取list缓存的内容
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<T> lGet(String key, long start, long end){
        try{
            return (List<T>)redisTemplate.opsForList().range(key,start,end);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key
     * @return
     */
    public long lGetListSize(String key){
        try{
            return redisTemplate.opsForList().size(key);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将某个值放入list缓存 右边right
     * @param key
     * @param value
     * @return
     */
    public boolean lrSet(String key,Object value){
        try{
            redisTemplate.opsForList().rightPush(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个值放入list缓存 右边right 并设置生命周期
     * @param key
     * @param value
     * @return
     */
    public boolean lrSet(String key,Object value,long time){
        try{
            redisTemplate.opsForList().rightPush(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个list放入缓存 右边right
     * @param key
     * @param value
     * @return
     */
    public boolean lrSet(String key,List<T> value){
        try {
            for(T t:value){
                lrSet(key,t);
            }
            //redisTemplate.opsForList().rightPushAll(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个list放入缓存 右边right 并设置生命周期
     * @param key
     * @param value
     * @param time
     * @return
     */
    public boolean lrSet(String key,List<T> value,long time){
        try{
            //redisTemplate.opsForList().rightPushAll(key,value);
            for(T t:value){
                lrSet(key,t);
            }
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }



    /**
     * 将某个值放入list缓存
     * @param key
     * @param value
     * @return
     */
    public boolean llSet(String key,Object value){
        try{
            redisTemplate.opsForList().leftPush(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个值放入list缓存 并设置生命周期
     * @param key
     * @param value
     * @return
     */
    public boolean llSet(String key,Object value,long time){
        try{
            redisTemplate.opsForList().leftPush(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个list放入缓存 左边left
     * @param key
     * @param value
     * @return
     */
    public boolean llSet(String key,List<T> value){
        try {
            for(T t:value){
                llSet(key,t);
            }
            //redisTemplate.opsForList().leftPushAll(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将某个list放入缓存 左边left 并设置整个缓存的声明周期
     * @param key
     * @param value
     * @param time
     * @return
     */
    public boolean llSet(String key,List<T> value,long time){
        try{
            for(T t:value){
                llSet(key,t);
            }
            //redisTemplate.opsForList().leftPushAll(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key
     * @param index
     * @param value
     * @return
     */
    public boolean lUpdateIndex(String key, long index,Object value){
        try{
            redisTemplate.opsForList().set(key,index,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除N个值为value
     * @param key
     * @param count
     * @param value
     * @return
     */
    public long lRemove(String key,long count,Object value){
        try{
            return redisTemplate.opsForList().remove(key,count,value);
        }catch (Exception e){
            e.printStackTrace();
            return 0;
        }
    }
}
